package org.fujene.memoryblock.repli;

import static org.fujene.memoryblock.repli.ReplicateContentBlock.LookupType.*;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

import org.fujene.communicate.hash.VirtualContentHash;
import org.fujene.constants.Constants;
import org.fujene.diskblock.DiskBlock;
import org.fujene.diskblock.repli.ReplicateContentDiskBlock;
import org.fujene.memoryblock.MemoryBlock;
import org.fujene.structure.Enumerates;
import org.fujene.structure.IndexQueue;
import org.fujene.structure.LookupPointer;
import org.fujene.toolbox.ByteConverter;

public class ReplicateContentBlock extends MemoryBlock{
	public static final int OPT_LEN = 0;
	public static final int OPT_APP = 1;
	public static final int OPT_TYPE = 2;
	public static final int OPTSIZE = 3;
	
	private volatile int opt_length = 0;
	private int opt_appendix = 0;
	private int opt_type = 0;

	public static enum LookupType{
		ALL, ID, NEXT, STR, APP
	}

	public ReplicateContentBlock(int bodySize, int ID, boolean isAllocate){
		super(bodySize, ID, Constants.MEMORY_TYPE_CONTENTS, isAllocate);
	}

	public ReplicateContentBlock(byte[] block, int bodySize, int ID, boolean isAllocate){
		super(block, bodySize, ID, Constants.MEMORY_TYPE_CONTENTS, isAllocate);
	}

	public class ContentsBody{
		private int from;
		private long contentID;
		private LookupPointer nextContent;
		private int strlen;
		private String str;
		private LookupPointer[] pointers;
		private int size;

		private LookupType type;

		// for reading
		public ContentsBody(int from, LookupType type){
			this.type = type;
			this.from = from;
			int fixedLength = PTR + 8 + (opt_appendix * PTR) + STRLEN;
			if(from + fixedLength >= bodySize){
				this.contentID = -1;
				this.nextContent = LookupPointer.EMPTY;
				this.strlen = 0;
				this.str = null;
				return;
			}

			if(type == ALL || type == NEXT) this.nextContent = LookupPointer.fromBytes(getBody(from, PTR));
			else this.nextContent = LookupPointer.EMPTY;

			from += PTR;
			if(type == ALL || type == ID) this.contentID = ByteConverter.byteToLong(getBody(from, 8));
			else this.contentID = -1L;

			from += 8;
			if(type == ALL || type == APP){
				this.pointers = new LookupPointer[opt_appendix];
				for(int i = 0; i < opt_appendix; i++){
					this.pointers[i] = LookupPointer.fromBytes(getBody(from, PTR));
					from += PTR;
				}
			}else{
				this.pointers = null;
				from += opt_appendix * PTR;
			}

			if(type == ALL || type == STR) this.strlen = ByteConverter.byteToInt(getBody(from, STRLEN));
			else this.strlen = 0;

			if(type == ALL || type == STR){
				if(this.strlen > 0){
					from += STRLEN;
					try{
						this.str = new String(getBody(from, strlen), "UTF-8");
					}catch(UnsupportedEncodingException e){}catch(Exception e){
						System.out.println("Error on reading " + this.from + " ID: " + contentID);
					}
				}else this.str = Constants.DELETED;
			}

			this.size = fixedLength + (strlen < 0 ? -1 * strlen : strlen);
		}

		// for writing
		public ContentsBody(int from, String str, long ID, LookupPointer nextContent, LookupPointer[] suppPtr){
			this.type = ALL;
			this.from = from;
			this.str = str;

			try{
				this.strlen = str.getBytes("UTF-8").length;
			}catch(UnsupportedEncodingException e){}

			this.contentID = ID;

			if(nextContent != null) this.nextContent = nextContent;
			else this.nextContent = LookupPointer.EMPTY;

			this.pointers = new LookupPointer[opt_appendix];
			for(int i = 0; i < this.pointers.length; i++){
				if(suppPtr[i] != null) this.pointers[i] = suppPtr[i];
				else this.pointers[i] = LookupPointer.EMPTY;
			}
			this.size = PTR + 8 + (opt_appendix * PTR) + STRLEN + strlen;
		}

		public LookupPointer getNextContent(){
			return nextContent;
		}

		public long getContentID(){
			return contentID;
		}

		public void setStrlen(int strlen){
			this.strlen = strlen;
		}

		public void write(boolean overwrite){
			int tmp = from;

			if(type == ALL || (overwrite && type == NEXT)) setBody(nextContent.toBytes(), tmp);
			tmp += PTR;

			if(type == ALL || (overwrite && type == ID))
				setBody(ByteConverter.longToByte(contentID, 8), tmp);
			tmp += 8;

			if(type == ALL || (overwrite && type == APP)) for(int i = 0; i < opt_appendix; i++){
				setBody(this.pointers[i].toBytes(), tmp);
				tmp += PTR;
			}
			else tmp += opt_appendix * PTR;

			if(type == ALL || (overwrite && type == STR))
				setBody(ByteConverter.intToByte(strlen, STRLEN), tmp);

			if(type == ALL){
				tmp += STRLEN;
				try{
					setBody(str.getBytes("UTF-8"), tmp);
				}catch(UnsupportedEncodingException e){}
			}
		}

		public String getStr(){
			return str;
		}
	}

	public String getContent(int ptr){
		ContentsBody ptred = new ContentsBody(ptr, ALL);
		return ptred.getStr();
	}

	public IndexQueue getContentAll(int ptr){
		ContentsBody ptred = new ContentsBody(ptr, ALL);
		return new IndexQueue(Enumerates.INDEX, ptred.getStr(), VirtualContentHash.getContentHash(ptred
				.getContentID()), ptred.getContentID(), 0, ptred.nextContent, ptred.size);
	}

	public LookupPointer[] getAppendixPointer(int ptr){
		ContentsBody ptred = new ContentsBody(ptr, APP);

		return ptred.pointers;
	}

	public LookupPointer getNextContentPointer(int ptr){
		ContentsBody ptred = new ContentsBody(ptr, NEXT);
		LookupPointer retPtr = ptred.getNextContent();
		if(retPtr.equals(LookupPointer.EMPTY)) return null;
		else return retPtr;
	}

	public long getContentID(int ptr){
		ContentsBody ptred = new ContentsBody(ptr, ID);
		return ptred.getContentID();
	}

	public int setContent(int ptr, String str, long ID, LookupPointer next, LookupPointer[] suppPtr){
		ContentsBody newContent = new ContentsBody(ptr, str, ID, next, suppPtr);
		newContent.write(true);
		return ptr + newContent.size;
	}

	public void setContentPointer(int ptr, LookupPointer next){
		if(next == null) return;
		ContentsBody newContent = new ContentsBody(ptr, NEXT);

		newContent.nextContent = next;
		newContent.write(true);
	}

	public void setAppendixPointer(int ptr, LookupPointer supp, int index){
		if(index >= opt_appendix) return;
		ContentsBody newContent = new ContentsBody(ptr, APP);

		newContent.pointers[index] = supp;
		newContent.write(true);
	}

	public void delete(int ptr){
		ContentsBody newContent = new ContentsBody(ptr, STR);

		// 文字列長さ-1で削除扱い
		newContent.setStrlen(-1 * newContent.strlen);
		newContent.write(true);
	}

	public boolean isDeleted(int ptr){
		ContentsBody deletedContent = new ContentsBody(ptr, STR);

		if(deletedContent.strlen < 0) return true;
		else return false;
	}

	// With appending points
	public int getNewPointer(String str){
		try{
			int length = PTR + 8 + (opt_appendix * PTR) + STRLEN + str.getBytes("UTF-8").length;
			if(this.bodyVacancy >= length){
				this.bodyVacancy -= length;
				return this.bodySize - this.bodyVacancy - length;
			}else{
				return -1;
			}
		}catch(UnsupportedEncodingException e){
			return -1;
		}
	}

	public int getCount(){
		return opt_length;
	}

	@Override
	public int getOptionSize(){
		return OPTSIZE;
	}

	@Override
	public byte[] getOptionByteStream(){
		ByteArrayOutputStream stream = new ByteArrayOutputStream();

		try{
			stream.write(ByteConverter.intToByte(opt_length, 4));
			stream.write(ByteConverter.intToByte(opt_appendix, 4));
			stream.write(ByteConverter.intToByte(opt_type, 4));
		}catch(IOException e){}
		return stream.toByteArray();
	}

	@Override
	public int getOption(int num){
		switch(num){
		case OPT_LEN:
			return opt_length;
		case OPT_APP:
			return opt_appendix;
		case OPT_TYPE:
			return opt_type;
		default:
			return 0;
		}
	}

	@Override
	public void setOption(int option, int value){
		if(option == OPT_LEN) opt_length = value;
		else if(option == OPT_APP) opt_appendix = value;
		else if(option == OPT_TYPE) opt_type = value;
	}

	@Override
	public String getClassName(){
		return new String("ExtendContentBlock");
	}

	@Override
	public byte[] getData() throws IOException{
		throw new NoSuchMethodError("Unimplemented method");
	}

	@Override
	public void setData(byte[] array) throws IOException{
		throw new NoSuchMethodError("Unimplemented method");
	}

	@Override
	public DiskBlock toDisk(int from, int to, String dir){
		ReplicateContentDiskBlock block = new ReplicateContentDiskBlock(this, dir);
		block.save(from, to, dir);
		
		return block;
	}
}
